from typing import Optional, Dict, Any, List
from sqlalchemy.orm import Session
from src.models.database.models import Plate, Position
from pydantic import BaseModel
from datetime import datetime

class SamplePlaceRequest(BaseModel):
    plate_name: str
    position_index: int
    sample_id: str
    task_id: Optional[str] = ""  # 任务ID
    task_type: Optional[str] = "place"  # 任务类型
    operator: Optional[str] = "system"  # 操作员

class SampleLocationResponse(BaseModel):
    sample_id: str
    plate_name: str
    position_index: int
    task_id: str
    task_type: str
    operator: str
    operation_time: str
    updated_at: str

class SampleService:
    def __init__(self, db: Session):
        self.db = db
    
    def place_sample(self, request: SamplePlaceRequest) -> Dict[str, Any]:
        """放置样品到指定料位"""
        # 检查料盘是否存在
        plate = self.db.query(Plate).filter(Plate.name == request.plate_name).first()
        if not plate:
            raise ValueError(f"料盘 '{request.plate_name}' 不存在")
        
        # 检查料位是否存在
        position = self.db.query(Position).filter(
            Position.plate_id == plate.id,
            Position.position_index == request.position_index
        ).first()
        
        if not position:
            raise ValueError(f"料盘 '{request.plate_name}' 的料位 {request.position_index} 不存在")
        
        # 检查料位是否可用
        if position.sample_id == "#999":
            raise ValueError(f"料位已禁用，无法放置样品")
        
        if position.sample_id and position.sample_id != "":
            raise ValueError(f"料位已被样品 '{position.sample_id}' 占用")
        
        # 检查当前料盘中是否已有相同样品ID（只管理当前料盘，不检查其他料盘）
        existing_in_current_plate = self.db.query(Position).filter(
            Position.plate_id == plate.id,
            Position.sample_id == request.sample_id
        ).first()
        
        if existing_in_current_plate:
            raise ValueError(f"样品 '{request.sample_id}' 已存在于当前料盘 '{request.plate_name}' 的料位 {existing_in_current_plate.position_index}")
        
        # 放置API只检查目标料位是否可用，不检查其他料盘
        # 如果需要移动样品，应该先调用移除API，再调用放置API
        
        # 放置样品并记录任务信息
        position.sample_id = request.sample_id
        position.status = "occupied"
        position.task_id = request.task_id or ""
        position.task_type = request.task_type or "place"
        position.operator = request.operator or "system"
        position.operation_time = datetime.now()
        position.updated_at = datetime.now()
        
        self.db.commit()
        
        return {
            "success": True,
            "message": f"样品 '{request.sample_id}' 已成功放置到料盘 '{request.plate_name}' 的料位 {request.position_index}",
            "sample_id": request.sample_id,
            "plate_name": request.plate_name,
            "position_index": request.position_index,
            "task_id": position.task_id,
            "task_type": position.task_type,
            "operator": position.operator
        }
    
    def remove_sample(self, plate_name: str, sample_id: str, task_id: str = "", operator: str = "system") -> Dict[str, Any]:
        """移除指定料盘中的样品"""
        # 检查料盘是否存在
        plate = self.db.query(Plate).filter(Plate.name == plate_name).first()
        if not plate:
            raise ValueError(f"料盘 '{plate_name}' 不存在")
        
        # 查找指定料盘中的样品
        position = self.db.query(Position).filter(
            Position.plate_id == plate.id,
            Position.sample_id == sample_id
        ).first()
        
        if not position:
            raise ValueError(f"样品 '{sample_id}' 在料盘 '{plate_name}' 中不存在")
        
        # 清空料位并记录任务信息
        position.sample_id = ""
        position.status = "available"
        position.task_id = task_id
        position.task_type = "remove"
        position.operator = operator
        position.operation_time = datetime.now()
        position.updated_at = datetime.now()
        
        self.db.commit()
        
        return {
            "success": True,
            "message": f"样品 '{sample_id}' 已从料盘 '{plate_name}' 的料位 {position.position_index} 移除",
            "sample_id": sample_id,
            "plate_name": plate_name,
            "position_index": position.position_index,
            "task_id": task_id,
            "operator": operator
        }
    
    def find_sample_location(self, sample_id: str) -> Optional[SampleLocationResponse]:
        """查询样品位置"""
        position = self.db.query(Position).filter(
            Position.sample_id == sample_id
        ).first()
        
        if not position:
            return None
        
        plate = self.db.query(Plate).filter(Plate.id == position.plate_id).first()
        
        return SampleLocationResponse(
            sample_id=sample_id,
            plate_name=plate.name,
            position_index=position.position_index,
            task_id=position.task_id or "",
            task_type=position.task_type or "",
            operator=position.operator or "",
            operation_time=position.operation_time.isoformat() if position.operation_time else "",
            updated_at=position.updated_at.isoformat() if position.updated_at else ""
        )

    def find_sample_location_in_plate(self, plate_name: str, sample_id: str) -> Optional[SampleLocationResponse]:
        """根据料盘名和样品ID查询样品位置"""
        # 检查料盘是否存在
        plate = self.db.query(Plate).filter(Plate.name == plate_name).first()
        if not plate:
            return None
        
        # 在指定料盘中查找样品
        position = self.db.query(Position).filter(
            Position.plate_id == plate.id,
            Position.sample_id == sample_id
        ).first()
        
        if not position:
            return None
        
        return SampleLocationResponse(
            sample_id=sample_id,
            plate_name=plate.name,
            position_index=position.position_index,
            task_id=position.task_id or "",
            task_type=position.task_type or "",
            operator=position.operator or "",
            operation_time=position.operation_time.isoformat() if position.operation_time else "",
            updated_at=position.updated_at.isoformat() if position.updated_at else ""
        )
    
    def get_first_available_position(self, plate_name: str) -> Optional[int]:
        """获取指定料盘的第一个可用料位索引"""
        plate = self.db.query(Plate).filter(Plate.name == plate_name).first()
        if not plate:
            return None
        
        position = self.db.query(Position).filter(
            Position.plate_id == plate.id,
            Position.sample_id == ""
        ).order_by(Position.position_index).first()
        
        return position.position_index if position else None
    
    def disable_position(self, plate_name: str, position_index: int, task_id: str = "", operator: str = "system") -> Dict[str, Any]:
        """禁用料位"""
        plate = self.db.query(Plate).filter(Plate.name == plate_name).first()
        if not plate:
            raise ValueError(f"料盘 '{plate_name}' 不存在")
        
        position = self.db.query(Position).filter(
            Position.plate_id == plate.id,
            Position.position_index == position_index
        ).first()
        
        if not position:
            raise ValueError(f"料盘 '{plate_name}' 的料位 {position_index} 不存在")
        
        if position.sample_id and position.sample_id != "" and position.sample_id != "#999":
            raise ValueError(f"料位被样品 '{position.sample_id}' 占用，无法禁用")
        
        position.sample_id = "#999"
        position.status = "disabled"
        position.task_id = task_id
        position.task_type = "disable"
        position.operator = operator
        position.operation_time = datetime.now()
        position.updated_at = datetime.now()
        
        self.db.commit()
        
        return {
            "success": True,
            "message": f"料盘 '{plate_name}' 的料位 {position_index} 已禁用",
            "plate_name": plate_name,
            "position_index": position_index,
            "task_id": task_id,
            "operator": operator
        }
    
    def enable_position(self, plate_name: str, position_index: int, task_id: str = "", operator: str = "system") -> Dict[str, Any]:
        """启用料位"""
        plate = self.db.query(Plate).filter(Plate.name == plate_name).first()
        if not plate:
            raise ValueError(f"料盘 '{plate_name}' 不存在")
        
        position = self.db.query(Position).filter(
            Position.plate_id == plate.id,
            Position.position_index == position_index
        ).first()
        
        if not position:
            raise ValueError(f"料盘 '{plate_name}' 的料位 {position_index} 不存在")
        
        if position.sample_id != "#999":
            raise ValueError(f"料位未被禁用，无需启用")
        
        position.sample_id = ""
        position.status = "available"
        position.task_id = task_id
        position.task_type = "enable"
        position.operator = operator
        position.operation_time = datetime.now()
        position.updated_at = datetime.now()
        
        self.db.commit()
        
        return {
            "success": True,
            "message": f"料盘 '{plate_name}' 的料位 {position_index} 已启用",
            "plate_name": plate_name,
            "position_index": position_index,
            "task_id": task_id,
            "operator": operator
        }
    
    def get_positions_by_task(self, task_id: str) -> List[Dict[str, Any]]:
        """根据任务ID查询相关料位"""
        positions = self.db.query(Position).filter(
            Position.task_id == task_id
        ).all()
        
        result = []
        for position in positions:
            plate = self.db.query(Plate).filter(Plate.id == position.plate_id).first()
            result.append({
                "plate_name": plate.name,
                "position_index": position.position_index,
                "sample_id": position.sample_id,
                "status": position.status,
                "task_type": position.task_type,
                "operator": position.operator,
                "operation_time": position.operation_time.isoformat() if position.operation_time else "",
                "updated_at": position.updated_at.isoformat() if position.updated_at else ""
            })
        
        return result 